﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using ABCat.Core.Plugins.Catalog;
using ABCat.Core.Plugins.DataProviders;
using ABCat.Core.Plugins.DataSets;

using HtmlAgilityPack;

namespace ABCat.Core.Plugins.Sites
{
	public abstract class SiteParserBase : ISiteParserPlugin
	{
		#region + Events +

		public event EventHandler Disposed;

		#endregion
		#region + Fields +

		protected readonly Context Context;

		#endregion
		#region + Properties +

		public Config Config		{get; set;}

		#endregion
		#region + Ctor +

		protected SiteParserBase(Context context)
		{
			Context = context;
		}

		#endregion
		#region + Logic +

		public void BeginDownloadRecordGroupsAsync(HashSet<string> recordGroupsKeys, Action<int, int, string> smallProgressCallback, Action<int, int, string> totalProgressCallback, Action<Exception> completedCallback, CancellationToken cancellationToken)
		{
			var mainConfig = Config.Load<MainConfig>(Context);

			Task.Factory.StartNew(() =>
				{
					try
					{
						var groupActualityPeriod = mainConfig.GroupActualityPeriod;
						int z = 0;
						using (var dbContainer = Context.CreateDbContainer(true))
						{
							IAudioBookGroup[] groups;
							if (recordGroupsKeys == null) groups = GetAllRecordGroups(dbContainer).ToArray();
							else groups = GetAllRecordGroups(dbContainer).Where(item => recordGroupsKeys.Contains(item.Key)).ToArray();

							foreach (var group in groups.Where(item => (DateTime.Now - item.LastUpdate).TotalDays > groupActualityPeriod))
							{
								totalProgressCallback(z, groups.Count(), "{0} из {1}".F(z, groups.Count()));
								DownloadRecordGroup(dbContainer, group, smallProgressCallback, cancellationToken);
								if (cancellationToken.IsCancellationRequested) break;
								dbContainer.SaveChanges();
								z++;
							}
						}
						completedCallback(null);
					}
					catch (Exception ex)
					{
						completedCallback(ex);
					}
				});
		}

		public void BeginDownloadRecordsAsync(HashSet<string> recordsKeys, bool cacheFirst, Action<int, int, string> smallProgressCallback, Action<int, int, string> totalProgressCallback, Action<Exception> completedCallback, CancellationToken cancellationToken)
		{
			var mainConfig = Config.Load<MainConfig>(Context);

			Task.Factory.StartNew(() =>
			{
				try
				{
					var recordActualityPeriod = mainConfig.RecordActualityPeriod;

					using (var dbContainer = Context.CreateDbContainer(true))
					{
						List<IAudioBook> records;

						if (recordsKeys == null) //ToDo || recordsIds.Count == dbContainer.Catalog.RecordSet.Count())
						{
							//records = new List<IAudioBook>();
							if (cacheFirst) records = dbContainer.AudioBookSet.GetRecordsAll().ToList();
							else records = dbContainer.AudioBookSet.GetRecordsUpdatedBefore(DateTime.Now.Subtract(TimeSpan.FromDays(recordActualityPeriod))).ToList();

							//foreach (var record in dbContainer.Catalog.RecordSet)
							//{
							//	if (cacheFirst || !record.LastUpdate.HasValue || ( DateTime.Now - record.LastUpdate.Value).TotalDays > recordActualityPeriod)
							//	{
							//		records.Add(record);
							//	}
							//}
						}
						else records = dbContainer.AudioBookSet.GetRecordsByKeys(recordsKeys).ToList(); // dbContainer.Catalog.RecordSet.Where(item => recordsIds.Contains(item.ID)).ToList();

						int savePerCount = cacheFirst ? 1000 : 5;

						var normalizerPlugin = Context.PluginsFactory.GetPlugins<INormalizationLogicPlugin>().First().CreateInstance<INormalizationLogicPlugin>(Context);

						for (int z = 0; z < records.Count; z++)
						{
							var record = records[z];
							totalProgressCallback(z, records.Count, "{0} из {1}".F(z, records.Count()));
							DownloadRecord(dbContainer, record, cacheFirst, smallProgressCallback, cancellationToken);
							record.Created = DateTime.Now;
							if (cancellationToken.IsCancellationRequested) break;
							if ((z > 0 && z % savePerCount == 0) || z == records.Count - 1)
							{
								var waitForResolve = records.Skip(z - savePerCount).Take(savePerCount).ToArray();
								normalizerPlugin.Normalize(waitForResolve, dbContainer);
								if (cancellationToken.IsCancellationRequested) break;
								dbContainer.AudioBookSet.AddChangedRecords(waitForResolve);
								dbContainer.SaveChanges();
								if (cancellationToken.IsCancellationRequested) break;
							}
						}

						if (!cancellationToken.IsCancellationRequested)
						{
							dbContainer.SaveChanges();
						}
						else dbContainer.AutoSaveChanges = false;
					}
					completedCallback(null);
				}
				catch (Exception ex)
				{
					completedCallback(ex);
				}
			});
		}

		public void BeginDownloadRecordSourcePageAsync(string recordKey, Action<string, Exception> completedCallback, CancellationToken cancellationToken)
		{
			Task.Factory.StartNew(() =>
				{
					try
					{
						completedCallback(GetRecordSourcePageString(recordKey, true, cancellationToken), null);
					}
					catch (Exception ex)
					{
						completedCallback(null, ex);
					}
				});
		}

		public abstract bool CheckForConfig(bool correct, out Config incorrectConfigs);

		public void Dispose()
		{
			Disposed.Fire(this);
		}

		public abstract IEnumerable<IAudioBookGroup> GetAllRecordGroups(IDbContainer container);

		protected virtual string CleanupRecordValue(string value, bool allowMultiLine, int maxLenght)
		{
			string result = allowMultiLine ? value : value.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)[0];

			do
			{
				value = result;
				result = value.Trim().Trim('\r').Trim('\n').Trim(':');
			}
			while (result != value);

			if (maxLenght>0 && result.Length > maxLenght)
			{
				result = result.Substring(0, maxLenght - 1) + "…";
			}

			return result;
		}

		/// <summary>
		/// Очистить страницу описания торрента от лишних элементов (заголовок, меню, скрипты и др.)
		/// </summary>
		/// <param name="document">Документ</param>
		protected virtual void ClearRecordPage(HtmlDocument document)
		{
			foreach (var script in document.GetNodes("script", "type", "text/javascript").ToArray())
			{
				script.ParentNode.RemoveChild(script);
			}
		}

		protected abstract void DownloadRecord(IDbContainer db, IAudioBook record, bool cacheFirst, Action<int, int, string> progressCallback, CancellationToken cancellationToken);

		protected abstract void DownloadRecordGroup(IDbContainer db, IAudioBookGroup recordGroup, Action<int, int, string> progressCallback, CancellationToken cancellationToken);

		protected abstract string GetRecordSourcePageString(string recordKey, bool cacheFirst, CancellationToken cancellationToken);

		protected abstract void ParseRecord(IDbContainer db, IAudioBook record, string postBodyHtml);

		#endregion
	}
}